/*file chiron src/sun.com/reg.js */

/*
reglib version 1.0.6
Copyright 2008
Released under MIT license
http://code.google.com/p/reglib/
*/

/*port-notes

     - remove the enclosure:
       window.reg = (function () {return reg})
     - change the reg = {} line to reg = exports
     - delete all import* functions
     - delete the globalError function
     - change occurrences of console.log to print
     - remove the section that adds console.log at the end
     - add the preamble comment after the author's original
       preamble and license

*/

/*preamble

    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    The license terms are stated in full in <license.rst> and at the end
    of all source files.

*/

var reg = exports;

if (window.Node && Node.prototype && !Node.prototype.contains) {
    Node.prototype.contains = function (arg) {
        return !!(this.compareDocumentPosition(arg) & 16);
    }
}

// #############################################################################
// #### SELECTORS ##############################################################
// #############################################################################

/*
A CSS-like selector API focusing on matching not traversal:
- new reg.Selector(selectorString)
- new reg.Selector(selectorString).matches(someElement)

For example:
var sel = new reg.Selector('div#foo > ul.bar > li');
var item = document.getElementById('myListItem');
if (sel.matches(item)) { ... }
*/

// precompiled patterns
var expressions = {
    leadSpace:  new RegExp("^\\s+"),
    tagName:    new RegExp("^([a-z_][a-z0-9_-]*)","i"),
    wildCard:   new RegExp("^\\*([^=]|$)"),
    className:  new RegExp("^(\\.([a-z0-9_-]+))","i"),
    id:         new RegExp("^(#([a-z0-9_-]+))","i"),
    att:        new RegExp("^(@([a-z0-9_-]+))","i"),
    matchType:  new RegExp("(^\\^=)|(^\\$=)|(^\\*=)|(^~=)|(^\\|=)|(^=)"),
    spaceQuote: new RegExp("^\\s+['\"]")
};

// constructor
reg.Selector=function(selString) {
    var exp = expressions;
    this.items = []; // for each comma-separated selector, this array has an item
    var itms = []; // this will be added to this.items
    var count = 0;
    var origSel = selString;
    while (selString.length>0) {
        if (count > 100) { throw new Error("failed parsing '"+origSel+"' stuck at '"+selString+"'"); }
        // get rid of any leading spaces
        var leadSpaceChopped = false;
        if (exp.leadSpace.test(selString)) {
            selString=selString.replace(exp.leadSpace,'');
            leadSpaceChopped = true;
        }

        // find tag name
        var tagNameMatch = exp.tagName.exec(selString);
        if (tagNameMatch) {
            if (itms.length > 0 && itms[itms.length-1].name=='tag') { itms.push({name:'descendant'}); }
            itms.push({name:'tag',tagName:tagNameMatch[1].toLowerCase()});
            selString=selString.substring(tagNameMatch[1].length);
            tagNameMatch=null;
            continue;
        }
        // explicit wildcard selector
        if (exp.wildCard.test(selString)) {
            if (itms.length > 0 && itms[itms.length-1].name=='tag') { itms.push({name:'descendant'}); }
            itms.push({name:'tag',tagName:'*'});
            selString = selString.substring(1);
            continue;
        }
        var classMatch = exp.className.exec(selString);
        var idMatch = exp.id.exec(selString);
        var attMatch = exp.att.exec(selString);
        if (classMatch || idMatch || attMatch) {
            // declare descendant if necessary
            if (leadSpaceChopped && itms.length>0 && itms[itms.length-1].name=='tag') { itms.push({name:'descendant'}); }
            // create a tag wildcard * if necessary
            if (itms.length==0 || itms[itms.length-1].name!='tag') { itms.push({name:'tag',tagName:'*'}); }
            var lastTag = itms[itms.length-1];
            // find class name, like .entry
            if (classMatch) {
                if (!lastTag.classNames) {
                    lastTag.classNames = [classMatch[2]];
                } else {
                    lastTag.classNames.push(classMatch[2]);
                }
                selString=selString.substring(classMatch[1].length);
                classMatch=null;
                continue;
            }
            // find id, like #content
            if (idMatch) {
                lastTag.id=idMatch[2];
                selString=selString.substring(idMatch[1].length);
                idMatch=null;
                continue;
            }
            // find attribute selector, like @src
            if (attMatch) {
                if (!lastTag.attributes) {
                    lastTag.attributes = [{name:attMatch[2]}];
                } else {
                    lastTag.attributes.push({name:attMatch[2]});
                }
                selString=selString.substring(attMatch[1].length);
                attMatch=null;
                continue;
            }
        }
        // find attribute value specifier
        var mTypeMatch=exp.matchType.exec(selString);
        if (mTypeMatch) {
            // this will determine how the matching is done
            // (lastTag should still be hanging around)
            if(lastTag && lastTag.attributes && !lastTag.attributes[lastTag.attributes.length-1].value){

                var lastAttribute = lastTag.attributes[lastTag.attributes.length-1];
                lastAttribute.matchType = mTypeMatch[0];
                
                selString=selString.substring(lastAttribute.matchType.length);
                if(selString.charAt(0)!='"'&&selString.charAt(0)!="'"){
                    if(exp.spaceQuote.test(selString)){selString=selString.replace(exp.leadSpace,'');}
                    else{throw new Error(origSel+" is invalid, single or double quotes required around attribute values");}
                }
                // it is enclosed in quotes, end is closing quote
                var q=selString.charAt(0);
                var lastQInd=selString.indexOf(q,1);
                if(lastQInd==-1){throw new Error(origSel+" is invalid, missing closing quote");}
                while(selString.charAt(lastQInd-1)=='\\'){
                    lastQInd=selString.indexOf(q,lastQInd+1);
                    if(lastQInd==-1){throw new Error(origSel+" is invalid, missing closing quote");}
                }
                lastAttribute.value=selString.substring(1,lastQInd);
                if      ('~=' == lastAttribute.matchType) { lastAttribute.valuePatt = new RegExp("(^|\\s)"+lastAttribute.value+"($|\\s)"); }
                else if ('|=' == lastAttribute.matchType) { lastAttribute.valuePatt = new RegExp("^"+lastAttribute.value+"($|\\-)"); }
                selString=selString.substring(lastAttribute.value.length+2);// +2 for the quotes
                continue;
            } else {
                throw new Error(origSel+" is invalid, "+mTypeMatch[0]+" appeared without preceding attribute identifier");
            }
            mTypeMatch=null;
        }
        // find child selector
        if (selString.charAt(0) == '>') {
            itms.push({name:'child'});
            selString=selString.substring(1);
            continue;
        }
        // find next sibling selector
        if (selString.charAt(0) == '+') {
            itms.push({name:'nextSib'});
            selString=selString.substring(1);
            continue;
        }
        // find after sibling selector
        if (selString.charAt(0) == '~') {
            itms.push({name:'followingSib'});
            selString=selString.substring(1);
            continue;
        }
        // find the comma separator
        if (selString.charAt(0) == ',') {
            this.items.push(itms);
            itms = [];
            selString = selString.substring(1);
            continue;
        }
        count++;
    }
    this.items.push(itms);
    this.selectorString=origSel;
    // do some structural validation
    for (var a=0;a<this.items.length;a++){
        var itms = this.items[a];
        if (itms.length==0) { throw new Error("illegal structure: '"+origSel+"' contains an empty set"); }
        if (itms[0].name!='tag') { throw new Error("illegal structure: '"+origSel+"' contains a dangling relation"); }
        if (itms[itms.length-1].name!='tag') { throw new Error("illegal structure: '"+origSel+"' contains a dangling relation"); }
        for(var b=1;b<itms.length;b++){
            if(itms[b].name!='tag'&&itms[b-1].name!='tag'){ throw new Error("illegal structure: '"+origSel+"' contains doubled up relations"); }
        }
    }
}

// returns string suitable for querySelector() and querySelectorAll()
function toQuerySelectorString(sel) {
    if (!sel.qss) {
        var itemStrings = [];
        for (var i=0; i<sel.items.length; i++) {
            var result = '';
            var item = sel.items[i];
            for (var j=0; j<item.length; j++) {
                var des = item[j];
                if (des.name=='tag') {
                    result += des.tagName;
                    if (des.classNames) { result += "." + des.classNames.join("."); }
                    if (des.id) { result += '#' + des.id; }
                    if (des.targeted) {  result += ':target'; }
                    if (des.attributes) {
                        
                        for (var k=0; k<des.attributes.length; k++) {
                            result += '[' + des.attributes[k].name;
                            if (des.attributes[k].matchType) {
                                result += des.attributes[k].matchType;
                                result += '"'+des.attributes[k].value.replace(/"/,'\\"')+'"';
                            }
                            result += ']';
                        }
                        
                    }
                } else if (des.name=='descendant') {
                    result += ' ';
                    continue;
                } else if (des.name=='child') {
                    result += ' > ';
                    continue;
                } else if (des.name=='followingSib') {
                    result += ' ~ ';
                    continue;
                } else if (des.name=='nextSib') {
                    result += ' + ';
                    continue;
                }
            }
            itemStrings.push(result);
        }
        sel.qss = itemStrings.join(', ');
    }
    return sel.qss;
}

// match against an element
reg.Selector.prototype.matches = function(el) {
    if (!el) { throw new Error(this.selectorString+' cannot be evaluated against '+el); }
    if (el.nodeType != 1) { throw new Error(this.selectorString+' cannot be evaluated against element of type '+el.nodeType); }
    commas:for (var a=0;a<this.items.length;a++) { // for each comma-separated selector
        var tempEl = el;
        var itms = this.items[a];
        for (var b=itms.length-1; b>=0; b--) { // loop backwards through the items
            var itm = itms[b];
            if (itm.name == 'tag') {
                if (!matchIt(tempEl, itm)) {
                    // these relational selectors require more extensive searching
                    if (tempEl && b < itms.length-1 && itms[b+1].name=='descendant') { tempEl=tempEl.parentNode; b++; continue; }
                    else if (tempEl && b < itms.length-1 && itms[b+1].name=='followingSib') { tempEl=tempEl.previousSibling; b++; continue; }
                    else { continue commas; } // fail this one
                }
            }
            else if (itm.name == 'nextSib') { tempEl = previousElement(tempEl); }
            else if (itm.name == 'followingSib') { tempEl = previousElement(tempEl); }
            else if (itm.name == 'child') { tempEl = tempEl.parentNode; }
            else if (itm.name == 'descendant') { tempEl = tempEl.parentNode; }
        }
        return true;
    }
    return false;
};

// subroutine for matches() above
function matchIt(el, itm) {
    // try to falsify as soon as possible
    if (!el) { return false; }
    if (el.nodeName.toLowerCase()!=itm.tagName && itm.tagName!='*') { return false; }
    if (itm.classNames) {
         for (var i=0; i<itm.classNames.length; i++) {
            if (!hasClassName(el, itm.classNames[i])) {
                return false;
            }
        }
    }
    if (itm.id && el.id != itm.id) { return false; }
    if (itm.attributes) {
        for (var i=0; i<itm.attributes.length; i++) {
            var itmAtt = itm.attributes[i];
            if (typeof el.hasAttribute != 'undefined') {
                if (!el.hasAttribute(itmAtt.name)) { return false; }
                var att = el.getAttribute(itmAtt.name);
            }else{
                if(el.nodeType!=1) {return false;}
                var att = el.getAttribute(itmAtt.name,2);
                if(itmAtt.name=='class'){att=el.className;}
                else if(itmAtt.name=='for'){att=el.htmlFor;}
                if(!att){return false;}
            }
            if (itmAtt.value) {
                if (itmAtt.matchType=='^='){
                    if (att.indexOf(itmAtt.value)!=0){return false;}
                } else if (itmAtt.matchType=='*='){
                    if (att.indexOf(itmAtt.value)==-1){return false;}
                } else if (itmAtt.matchType=='$='){
                    if (att.indexOf(itmAtt.value)!=att.length-itmAtt.value.length){return false;}
                } else if (itmAtt.matchType=='='){
                    if (att!=itmAtt.value){return false;}
                } else if ('|='==itmAtt.matchType || '~='==itmAtt.matchType){
                    if (!itmAtt.valuePatt.test(att)){return false;}
                }else{
                    if(!itmAtt.matchType){throw new Error("illegal structure, parsed selector cannot have null or empty attribute match type");}
                    else{throw new Error("illegal structure, parsed selector cannot have '"+itm.matchType+"' as an attribute match type");}
                }
            }
        }
    }
    return true;
}

// gets the tag names that the selector represents
function getTagNames(sel) {
    var hash = {}; // this avoids dupes
    for (var a=0;a<sel.items.length;a++){
        hash[sel.items[a][sel.items[a].length-1].tagName]=null;
    }
    var result = [];
    for (var tag in hash){if(hash.hasOwnProperty(tag)){result.push(tag);}}
    return result;
}

// #############################################################################
// #### DOM HELPERS ############################################################
// #############################################################################

/*
A bunch of DOM convenience methods (alias names in braces):

CLASSNAMES
- reg.addClassName(el, cName)..............................{acn}
- reg.getElementsByClassName(cNames[, ctxNode[, tagName]]).{gebcn}
- reg.hasClassName(el, cName)..............................{hcn}
- reg.matchClassName(el, regexp)...........................{mcn}
- reg.removeClassName(el, cName)...........................{rcn}
- reg.switchClassName(el, cName1, cName2)..................{scn}
- reg.toggleClassName(el, cName)...........................{tcn}

SELECTORS
- reg.elementMatchesSelector(el, selString)................{matches}
- reg.getElementsBySelector(selString[, ctxNode])..........{gebs}

OTHER
- reg.elementText(el)......................................{elemText}
- reg.getElementById().....................................{gebi}
- reg.getElementsByTagName(tagName[, ctxNode]).............{gebtn}
- reg.getParent(el, selString)
- reg.innerWrap(el, wrapperEl)
- reg.insertAfter(insertMe, afterThis)
- reg.newElement(tagName[, attObj[, contents]])............{elem}
- reg.nextElement(el)......................................{nextElem}
- reg.outerWrap(el, wrapperEl)
- reg.previousElement(el)..................................{prevElem}
*/

var clPatts={};// cache compiled classname regexps
var cSels={};// cache compiled selectors

// TEST FOR CLASS NAME
function hasClassName(element, cName) {
    if (!clPatts[cName]) { clPatts[cName] = new RegExp("(^|\\s)"+cName+"($|\\s)"); }
    return element.className && clPatts[cName].test(element.className);
}

// ADD CLASS NAME
function addClassName(element, cName) {
    if (!hasClassName(element, cName)) {
        element.className += ' ' + cName;
    }
}

// REMOVE CLASS NAME
function removeClassName(element, cName) {
    if (!clPatts[cName]) { clPatts[cName] = new RegExp("(^|\\s+)"+cName+"($|\\s+)"); }
    element.className = element.className.replace(clPatts[cName], ' ');
}

// TOGGLE CLASS NAME
function toggleClassName(element, cName) {
    if (hasClassName(element, cName)) { removeClassName(element, cName); }
    else { addClassName(element, cName); }
}

// SWITCH CLASS NAME A->B, B->A
function switchClassName(element, cName1, cName2) {
    if (cName1 == cName2) { throw new Error("cName1 and cName2 both equal "+cName1); }
    var has1 = hasClassName(element, cName1);
    var has2 = hasClassName(element, cName2);
    if (has1 && has2) { removeClassName(element, cName2); }
    else if (!has1 && !has2) { addClassName(element, cName1); }
    else if (has1) { removeClassName(element, cName1); addClassName(element, cName2); }
    else { removeClassName(element, cName2); addClassName(element, cName1); }
}

// TEST FOR CLASS NAME BY REGEXP
function matchClassName(element, pattern){
    var cNames = element.className.split(' ');
    for (var a=0; a<cNames.length; a++){
        var matches = cNames[a].match(pattern);
        if (matches) { return matches; }
    }
    return null;
}

// TEST AGAINST SELECTOR
function elementMatchesSelector(element, selString){
    if(!cSels[selString]){cSels[selString]=new reg.Selector(selString);}
    return cSels[selString].matches(element);
}

// FIND PREVIOUS ELEMENT
function previousElement(el) {
    var prev = el.previousSibling;
    while(prev && prev.nodeType!=1){prev=prev.previousSibling;}
    return prev;
}

// FIND NEXT ELEMENT
function nextElement(el) {
    var next = el.nextSibling;
    while(next && next.nodeType!=1){next=next.nextSibling;}
    return next;
}

// ADD INNER WRAPPER
function innerWrap(el, wrapperEl) {
    var nodes = el.childNodes;
    while (nodes.length > 0) {
        var myNode = nodes[0];
        el.removeChild(myNode);
        wrapperEl.appendChild(myNode);
    }
    el.appendChild(wrapperEl);
}

// ADD OUTER WRAPPER
function outerWrap(el, wrapperEl) {
    el.parentNode.insertBefore(wrapperEl, el);
    el.parentNode.removeChild(el);
    wrapperEl.appendChild(el);
}

// GET PARENT
function getParent(el, selString) {
    var parsedSel = new reg.Selector(selString);
    while (el.parentNode) {
        el = el.parentNode;
        if (el.nodeType==1 && parsedSel.matches(el)) { return el; }
    }
    return null;
}

// INSERT AFTER
function insertAfter(insertMe, afterThis){
    var beforeThis = afterThis.nextSibling;
    var parent = afterThis.parentNode;
    if (beforeThis) { parent.insertBefore(insertMe, beforeThis); }
    else { parent.appendChild(insertMe); }
}

// SHORTCUT FOR BUILDING ELEMENTS
function newElement(name, atts, content) {
    // name: e.g. 'div', 'div.foo', 'div#bar', 'div.foo#bar', 'div#bar.foo'
    // atts: (optional) e.g. {'href':'page.html','target':'_blank'}
    // content: (optional) either a string, or an element, or an arry of strings or elements
    if (name.indexOf('.') + name.indexOf('#') > -2) {
        var className = (name.indexOf('.') > -1) ? name.replace(/^.*\.([^\.#]*).*$/,"$1") : "";
        var id = (name.indexOf('#') > -1) ? name.replace(/^.*#([^\.#]*).*$/,"$1") : "";
        name = name.replace(/^([^\.#]*).*$/,'$1');
    }
    var e = document.createElement(name);
    if (className) { e.className = className; }
    if (id) { e.id = id; }
    if (atts) {
        for (var key in atts) {
            // setAttribute() has shaky support, try direct methods first
            if (!atts.hasOwnProperty(key)) { continue; }
            if (key == 'class') { e.className = e.className ? e.className += ' ' + atts[key] : atts[key]; }
            else if (key == 'for') { e.htmlFor = atts[key]; }
            else if (key.indexOf('on') == 0) { e[key] = atts[key]; }
            else {
                e.setAttribute(key, atts[key]);
            }
        }
    }
    if (content) {
        if (!(content instanceof Array)) {
            content = [content];
        }
        for (var a=0; a<content.length; a++) {
            if (typeof content[a] == 'string') {
                e.appendChild(document.createTextNode(content[a]));
            }else{
                e.appendChild(content[a]);
            }
        }
    }
    if (name.toLowerCase() == 'img' && !e.alt) { e.alt = ''; }
    return e;
}

// GRAB JUST THE TEXTUAL DATA OF AN ELEMENT
function elementText(el) {
    // <a id="foo" href="page.html">click <b>here</b></a>
    // elementText(document.getElementById('foo')) == "click here"
    var r = '';
    if (el.alt) { r += el.alt; }
    r += el.innerHTML.replace(/<[a-z0-9_-]+ [^>]+alt="([^">]+)[^>]+>/ig,'$1').replace(/<[^>]+>/ig,'');
    var d = newElement('div');
    d.innerHTML = r;
    r = (d.childNodes[0]) ? d.childNodes[0].data : '';
    d = null;
    return r;
}

// GET ELEMENT BY ID
function getElementById(id) { return document.getElementById(id); }

// GET ELEMENTS BY TAG NAME
function getElementsByTagName(tag, contextNode) {
    if(!contextNode){contextNode=document;}
    return contextNode.getElementsByTagName(tag);
}

// GET ELEMENTS BY SELECTOR
var classTest = /^\s*([a-z0-9_-]+)?\.([a-z0-9_-]+)\s*$/i;
var idTest = /^\s*([a-z0-9_-]+)?\#([a-z0-9_-]+)\s*$/i;
function getElementsBySelector(selString, contextNode) {
    contextNode = contextNode || window.document.documentElement;
    var result = [];
    var cMat, iMat;
    if (cMat = selString.match(classTest)) {
        var cl = cMat[2];
        var tg = cMat[1];
        result = reg.gebcn(cl, contextNode, tg);
    } else if (iMat = selString.match(idTest)) {
        var id = iMat[2];
        var tg = iMat[1];
        var el = reg.gebi(id);
        if (el && contextNode.contains(el) && reg.matches(el, selString)) { result[0] = el; }
    } else {
        if (!cSels[selString]) { cSels[selString] = new reg.Selector(selString); }
        var sel = cSels[selString];
        if (contextNode.querySelectorAll) {
            var qlist = contextNode.querySelectorAll(toQuerySelectorString(sel));
            for (var i=0; i<qlist.length; i++) {
                result[result.length] = qlist[i];
            }
        } else {
            var tagNames = getTagNames(sel);
            for (var a=0; a<tagNames.length; a++) {
                var els = getElementsByTagName(tagNames[a], contextNode);
                for (var b=0, el; el=els[b++];) {
                    if (el.nodeType!=1) { continue; }
                    if (sel.matches(el)) { result.push(el); }
                }
            }
        }
    }
    return result;
}

// GET ELEMENTS BY CLASS NAME
function getElementsByClassName(classNames, contextNode, tag) {
    contextNode = (contextNode) ? contextNode : document;
    tag = (tag) ? tag.toLowerCase() : '*';
    var results = [];
    if (document.getElementsByClassName) {
        // traverse natively
        var liveList = contextNode.getElementsByClassName(classNames);
        if (tag != '*') {
            for (var i=0; i<liveList.length; i++) {
                var el = liveList[i];
                if (tag == el.nodeName.toLowerCase()) {
                    results.push(el);
                }
            }
        } else {
            for (var i=0; i<liveList.length; i++) { results.push(liveList[i]); }
        }
    } else {
        classNames = classNames.split(/\s+/);
        if (document.evaluate) {
            // traverse w/ xpath
            var xpath = ".//"+tag;
            var len = classNames.length;
            for(var i=0; i<len; i++) {
                xpath += "[contains(concat(' ', @class, ' '), ' " + classNames[i] + " ')]";
            }
            var xpathResult = document.evaluate(xpath, contextNode, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, xpathResult);
            var el;
            while (el = xpathResult.iterateNext()) {
                results.push(el);
            }
        } else {
            // traverse w/ dom
            var els = (tag=='*'&&contextNode.all) ? contextNode.all : getElementsByTagName(tag,contextNode);
            elements:for (var i=0,el;el=els[i++];) {
                for (var j=0; j<classNames.length; j++) {
                    if (!hasClassName(el, classNames[j])) { continue elements; }
                }
                results.push(el);
            }
        }
    }
    return results;
}

var helpers = {
    hasClassName:           hasClassName,
    addClassName:           addClassName,
    removeClassName:        removeClassName,
    toggleClassName:        toggleClassName,
    switchClassName:        switchClassName,
    matchClassName:         matchClassName,
    elementMatchesSelector: elementMatchesSelector,
    previousElement:        previousElement,
    nextElement:            nextElement,
    innerWrap:              innerWrap,
    outerWrap:              outerWrap,
    getParent:              getParent,
    insertAfter:            insertAfter,
    newElement:             newElement,
    elementText:            elementText,
    getElementById:         getElementById,
    getElementsByTagName:   getElementsByTagName,
    getElementsBySelector:  getElementsBySelector,
    getElementsByClassName: getElementsByClassName
};

// aliases
helpers.hcn      = helpers.hasClassName;
helpers.acn      = helpers.addClassName;
helpers.rcn      = helpers.removeClassName;
helpers.tcn      = helpers.toggleClassName;
helpers.scn      = helpers.switchClassName;
helpers.mcn      = helpers.matchClassName;
helpers.matches  = helpers.elementMatchesSelector;
helpers.prevElem = helpers.previousElement;
helpers.nextElem = helpers.nextElement;
helpers.elem     = helpers.newElement;
helpers.elemText = helpers.elementText;
helpers.gebi     = helpers.getElementById;
helpers.gebtn    = helpers.getElementsByTagName;
helpers.gebs     = helpers.getElementsBySelector;
helpers.gebcn    = helpers.getElementsByClassName;

// add it to reg
for (var func in helpers) {
    if(!helpers.hasOwnProperty(func)) { continue; }
    if (reg[func]) { throw new Error("Already exists under reg: "+func); }
    else { reg[func] = helpers[func]; }
}

// #############################################################################
// #### X-BROWSER EVENTS #######################################################
// #############################################################################

/*
Event attachment and detachment:
- reg.addEvent(elmt,evt,handler,cptr)
- reg.calcelDefault(evt)
- reg.getTarget(evt)
*/

var memEvents = {};
var aMemInd = 0;
function rememberEvent(elmt,evt,handle,cptr){
    var memInd = aMemInd++;
    var key = "mem"+memInd;
    memEvents[key] = {
        element: elmt,
        event: evt,
        handler: handle,
        capture: cptr
    };
    return memInd;
}
function cleanup(){return;
    for (var key in memEvents) {
        var matches = key.match(/^mem(\d+)$/);
        if (!matches) { continue; }
        removeEvent(parseInt(matches[1]));
    }
}

// get the element on which the event occurred
function getTarget(e) {
    if (!e) { e = window.event; }
    if (e.target) { var targ = e.target; }
    else if (e.srcElement) { var targ = e.srcElement; }
    if (targ.nodeType == 3) { targ = targ.parentNode; } // safari hack
    return targ;
}

// get the element on which the event occurred
function getRelatedTarget(e) {
    if (!e) { e = window.event; }
    var rTarg = e.relatedTarget;
    if (!rTarg) {
        if ('mouseover'==e.type) { rTarg = e.fromElement; }
        if ('mouseout'==e.type) { rTarg = e.toElement; }
    }
    return rTarg;
}

// cancel default action
function cancelDefault(e) {
    if (typeof e.preventDefault != 'undefined') { e.preventDefault(); return; }
    e.returnValue=false;
}

// cancel bubble
function cancelBubble(e) {
    if (typeof e.stopPropagation != 'undefined') { e.stopPropagation(); return; }
    e.cancelBubble=true;
}

// generic event adder, plus memory leak prevention
// returns an int mem that you can use to later remove that event removeEvent(mem)
function addEvent(elmt,evt,handler,cptr) {
    cptr=(cptr)?true:false;
    if(elmt.addEventListener){
        elmt.addEventListener(evt,handler,cptr);
        return rememberEvent(elmt,evt,handler,cptr);
    }else if(elmt.attachEvent){
        var actualHandler = function(){handler.call(elmt,window.event);};
        elmt.attachEvent("on"+evt,actualHandler);
        return rememberEvent(elmt,evt,actualHandler);
    }
}

// event remover
function removeEvent(memInt) {
    var eo = memEvents["mem"+memInt];
    if (eo) {
        var el=eo.element;
        if(el.removeEventListener) {
            el.removeEventListener(eo.event, eo.handler, eo.capture);
            return true;
        } else if(el.detachEvent) {
            el.detachEvent('on'+eo.event, eo.handler);
            return true;
        }
    }
    return false;
}

// fight memory leaks in ie
addEvent(window,'unload',cleanup);

var events = {
    getTarget:        getTarget,
    getRelatedTarget: getRelatedTarget,
    cancelDefault:    cancelDefault,
    addEvent:         addEvent,
    removeEvent:      removeEvent,
    cancelBubble:     cancelBubble
};

for (var func in events) {
    if(!events.hasOwnProperty(func)) { continue; }
    if (reg[func]) { throw new Error("Already exists under reg: "+func); }
    else { reg[func] = events[func]; }
}

// #############################################################################
// #### ON(DOM)LOAD ACTIONS ####################################################
// #############################################################################

/*
Add actions to run onload:
- reg.preSetup(func)
- reg.setup(selString, func, firstTimeOnly)
- reg.postSetup(func)

!!! WARNING !!!
On browsers *without* native querySelector() support
reg.setup makes page load time O(MN)
where M is the number of calls to reg.setup()
and N is the number of elements on the page
*/

// these contain lists of things to do
var preSetupQueue=[];
var setupQueue={};
var postSetupQueue=[];

// traverse and act onload
reg.setup=function(selector, setup, firstTimeOnly){
    firstTimeOnly=(firstTimeOnly)?true:false;
    var sq=setupQueue;
    var parsedSel = new reg.Selector(selector);
    var tagNames=getTagNames(parsedSel);
    var regObj={
        selector:parsedSel,
        setup:setup,
        ran:false,
        firstTimeOnly:firstTimeOnly
    };
    for(var a=0;a<tagNames.length;a++){
        var tagName = tagNames[a];
        if(!sq[tagName]){sq[tagName]=[regObj];}
        else{sq[tagName].push(regObj);}
    }
};
// do this before setup
reg.preSetup=function(fn){preSetupQueue.push(fn);};
// do this after setup
reg.postSetup=function(fn){postSetupQueue.push(fn);};

// (re)run setup functions
var runSetupFunctions = reg.rerun = function(el, noClobber){
    function runIt(el, regObj){
        regObj.setup.call(el);
        regObj.ran=true;
    }
    var start = new Date().getTime();
    if (typeof el.clobberable != 'undefined' && el.clobberable && noClobber) { return; }
    var doc=(el)?el:document;
    var sq=setupQueue;
    var sqIsEmpty=true;
    for (var tagName in sq) {
        if(!sq.hasOwnProperty(tagName)) { continue; }
        sqIsEmpty = false;
        break;
    }

    if (el.querySelector) {

        //####################################
        //querySelector() branch

        var qSelResults = [];
        for (var tagName in sq) {
            if(!sq.hasOwnProperty(tagName)) { continue; }
            var regObjArray = sq[tagName];
            for (var i=0; i<regObjArray.length; i++) {
                var regObj = regObjArray[i];
                if (regObj.firstTimeOnly) {
                    if (regObj.ran) { continue; }
                    try {
                        var elmt = el.querySelector(toQuerySelectorString(regObj.selector));
                        if (elmt) { qSelResults.push({el:elmt,regObj:regObj}); }
                    } catch (ex) {
                        print("querySelector('"+toQuerySelectorString(regObj.selector)+"') threw "+ex);
                        continue;
                    }
                } else {
                    try {
                        var elmts = el.querySelectorAll(toQuerySelectorString(regObj.selector));
                        for (var j=0; j<elmts.length; j++) {
                            qSelResults.push({el:elmts[j],regObj:regObj});
                        }
                    } catch (ex) {
                        print("querySelectorAll('"+toQuerySelectorString(regObj.selector)+"') threw "+ex);
                        continue;
                    }
                }
            }
        }
        for (var i=0; i<qSelResults.length; i++) {
            runIt(qSelResults[i].el, qSelResults[i].regObj);
        }
    } else if (!sqIsEmpty) {

        //####################################
        //old branch

        var elsList=getElementsByTagName('*',doc);

        //dump live list to static list
        for (var i=elsList.length-1, els=[]; i>=0; i--) {
            els[i] = elsList[i];
        }

        // crawl the dom
        for(var a=0,elmt;elmt=els[a++];){
            if (elmt.nodeType!=1){continue;}//for ie7
            var lcNodeName=elmt.nodeName.toLowerCase();
            var regObjArrayAll=sq['*'];
            var regObjArrayTag=sq[lcNodeName];

            // any wildcards?
            if(regObjArrayAll){
                for(var b=0;b<regObjArrayAll.length;b++){
                    var regObj=regObjArrayAll[b];
                    if(regObj.firstTimeOnly && regObj.ran){continue;}
                    var matches = regObj.selector.matches(elmt);
                    if(matches){runIt(elmt, regObj);}
                }
            }

            // any items match this specific tag?
            if(regObjArrayTag){
                for(var b=0;b<regObjArrayTag.length;b++){
                    var regObj=regObjArrayTag[b];
                    if(regObj.firstTimeOnly && regObj.ran){continue;}
                    var matches = regObj.selector.matches(elmt);
                    if(matches){runIt(elmt, regObj);}
                }
            }
        }
    }
    el.clobberable = true;
    var runtime = new Date().getTime() - start;
    if(!reg.setupTime){ reg.setupTime=runtime; }
    reg.lastSetupTime=runtime;
}

var ie6 = navigator.appVersion.indexOf('MSIE 6.0') != -1;
if (!ie6) {
    addClassName(document.documentElement, 'regloading');
}
var loadFuncRan = false;
function loadFunc(e) {
    if (!loadFuncRan) {
        for(var a=0;a<preSetupQueue.length;a++){
            preSetupQueue[a]();
        }
        runSetupFunctions(document, true);
        for(var a=0;a<postSetupQueue.length;a++){
            postSetupQueue[a]();
        }
        loadFuncRan = true;
        if (!ie6) {
            // unfortunately this causes hangs and laborious redraws in ie6
            removeClassName(document.documentElement, 'regloading');
            addClassName(document.documentElement, 'regloaded');
        }
    }
}

// contents of loadFunc only execute once, this sidesteps user agent sniffing
addEvent(window, 'load', loadFunc);
addEvent(window, 'DOMContentLoaded', loadFunc);

// #############################################################################
// #### EVENT DELEGATION #######################################################
// #############################################################################

/*
The main purpose of reglib is event delegation:
- reg.click(selString, handler, depth)
- reg.hover(selString, overHandler, outHandler, depth)
- reg.focus(selString, focusHandler, blurHandler, depth)
- reg.key(selString, downHandler, pressHandler, upHandler, depth)

Note: delegated events are active before page load, and remain
active throughout arbitrary rewrites of the DOM.
*/

// these contain the event handling functions
var clickHandlers = {};
var mouseDownHandlers = {};
var mouseUpHandlers = {};
var doubleClickHandlers = {};
var mouseOverHandlers = {};
var mouseOutHandlers = {};
var focusHandlers = {};
var blurHandlers = {};
var keyDownHandlers = {};
var keyPressHandlers = {};
var keyUpHandlers = {};

// returns first arg that's a number
function getDepth(fargs){
    var result = null;
    for (var i=2; i<fargs.length; i++) {
        if (!isNaN(parseInt(fargs[i]))) {
            result = fargs[i];
            break;
        }
    }
    if(result===null){result=-1;}
    if(result<-1){throw new Error("bad arg for depth, must be -1 or higher");}
    return result;
}

// add a handler function
function pushFunc(selStr, handlerFunc, depth, handlers, hoverFlag, memInd) {
    if(!handlerFunc || typeof handlerFunc != "function"){return;}
    var parsedSel = new reg.Selector(selStr);
    if(!handlers[selStr]) {handlers[selStr]=[];}
    var selHandler = {
        selector:parsedSel,
        handle:handlerFunc,
        depth:depth,
        hoverFlag:hoverFlag,
        paused:false
    };
    handlers[selStr].push(selHandler);
    if (!pauseResumeArray[memInd]) { pauseResumeArray[memInd] = []; }
    pauseResumeArray[memInd].push(selHandler);
}

// to keep track of events for later pause and resume
var dMemInd = 0;
var pauseResumeArray = [];

// click
reg.click=function(selStr, clickFunc, downFunc, upFunc, doubleFunc){
    var depth = getDepth(arguments);
    var memInd = dMemInd++;
    pushFunc(selStr, clickFunc,  depth, clickHandlers,       false, memInd);
    pushFunc(selStr, downFunc,   depth, mouseDownHandlers,   false, memInd);
    pushFunc(selStr, upFunc,     depth, mouseUpHandlers,     false, memInd);
    pushFunc(selStr, doubleFunc, depth, doubleClickHandlers, false, memInd);
    return memInd;
};

// mouse over and out
reg.hover=function(selStr, overFunc, outFunc){
    var depth = getDepth(arguments);
    var memInd = dMemInd++;
    pushFunc(selStr, overFunc, depth, mouseOverHandlers, true, memInd);
    pushFunc(selStr, outFunc,  depth, mouseOutHandlers,  true, memInd);
    return memInd;
};

// focus and blur
reg.focus=function(selStr, focusFunc, blurFunc){
    var depth = getDepth(arguments);
    var memInd = dMemInd++;
    pushFunc(selStr, focusFunc, depth, focusHandlers, false, memInd);
    pushFunc(selStr, blurFunc,  depth, blurHandlers,  false, memInd);
    return memInd;
};

// key press
reg.key=function(selStr, downFunc, pressFunc, upFunc){
    var depth = getDepth(arguments);
    var memInd = dMemInd++;
    pushFunc(selStr, downFunc,  depth, keyDownHandlers,  false, memInd);
    pushFunc(selStr, pressFunc, depth, keyPressHandlers, false, memInd);
    pushFunc(selStr, upFunc,    depth, keyUpHandlers,    false, memInd);
    return memInd;
};

// stops execution of event
reg.pause = function(memInd) {
    if (!(memInd in pauseResumeArray)) { return; }
    var arr = pauseResumeArray[memInd];
    for (var i=0; i<arr.length; i++) { arr[i].paused = true; }
};

// starts execution of event
reg.resume = function(memInd) {
    if (!(memInd in pauseResumeArray)) { return; }
    var arr = pauseResumeArray[memInd];
    for (var i=0; i<arr.length; i++) { arr[i].paused = false; }
};

// the delegator
function delegate(selectionHandlers, event) {
    var targ = getTarget(event);
    if (selectionHandlers) {
        for (var sel in selectionHandlers) {
            if(!selectionHandlers.hasOwnProperty(sel)) { continue; }
            for(var a=0; a<selectionHandlers[sel].length; a++) {
                var selHandler=selectionHandlers[sel][a];
                if (selHandler.paused) { continue; }
                var depth = (selHandler.depth==-1) ? 100 : selHandler.depth;
                var el = targ;
                for (var b=-1; b<depth && el && el.nodeType == 1; b++, el=el.parentNode) {
                    if (selHandler.selector.matches(el)) {
                        // replicate mouse enter/leave
                        if (selHandler.hoverFlag) {
                            var relTarg = getRelatedTarget(event);
                            if (relTarg && (el.contains(relTarg) || el == relTarg)) {
                                break;
                            }
                        }
                        var retVal=selHandler.handle.call(el,event);
                        // if they return false from the handler, cancel default
                        if(retVal!==undefined && !retVal) {
                            cancelDefault(event);
                        }
                        break;
                    }
                }
            }
        }
    }
}

if(typeof document.onactivate == 'object'){
    var focusEventType = 'activate';
    var blurEventType = 'deactivate';
}else{
    var focusEventType = 'focus';
    var blurEventType = 'blur';
}

// attach the events
addEvent(document.documentElement,'click',        function(e){delegate(clickHandlers,      e);});
addEvent(document.documentElement,'mousedown',    function(e){delegate(mouseDownHandlers,  e);});
addEvent(document.documentElement,'mouseup',      function(e){delegate(mouseUpHandlers,    e);});
addEvent(document.documentElement,'dblclick',     function(e){delegate(doubleClickHandlers,e);});
addEvent(document.documentElement,'keydown',      function(e){delegate(keyDownHandlers,    e);});
addEvent(document.documentElement,'keypress',     function(e){delegate(keyPressHandlers,   e);});
addEvent(document.documentElement,'keyup',        function(e){delegate(keyUpHandlers,      e);});
addEvent(document.documentElement, focusEventType,function(e){delegate(focusHandlers,      e);},true);
addEvent(document.documentElement, blurEventType, function(e){delegate(blurHandlers,       e);},true);
addEvent(document.documentElement,'mouseover',    function(e){delegate(mouseOverHandlers,  e);});
addEvent(document.documentElement,'mouseout',     function(e){delegate(mouseOutHandlers,   e);});

// handy for css
addClassName(document.documentElement, 'regenabled');


/*license

    Legal
    =======
    
    Chiron is a component of the Tale web-game project.
    
    See <credit.txt> for a complete list of
    contributions and their licenses.  All contributions are provided
    under permissive, non-viral licenses including MIT, BSD, Creative Commons
    Attribution 2.5, Public Domain, or Unrestricted.
    
    
    License
    =======
    
    Copyright (c) 2002-2008 Kris Kowal <http://cixar.com/~kris.kowal>
    MIT License
    
    
    MIT License
    -----------
    
    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use,
    copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following
    conditions:
    
    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
    OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
    OTHER DEALINGS IN THE SOFTWARE.

*/

